Rust 提供強大而安全的字串切片功能,可以有效地從字串中提取部分內容,不需複製也不會轉移所有權。這邊依照四個主題說明:從 String 創建切片、字串字面值與切片、len() 與 UTF-8 字元長度、語法簡寫技巧(Syntactic shortcuts)。
fn main() {
let full_name = String::from("Chen Roger"); // 創建一個 String
let slices_name = &full_name[0..4]; // 從 String 創建一個字串切片
println!("{}", slices_name); // 印出 "Chen"
}
let slices_name = &full_name[0..4]
& (借用操作符):我們使用借用操作符 & 來表示我們要創建一個引用,而不是取得數據的所有權。字串切片本質上就是一個引用。full_name 這個 String 中獲取一部分。[0..4] (範圍語法): 這指定了我們想要切片的部分。重要的是,這裡的索引是 基於位元組 (byte) 的索引,而不是基於字元 (character) 的索引。
[0..4] 表示從索引 0 開始,直到索引 4 (但不包含索引 4) 的所有位元組。這恰好是 "Chen" 這四個字元所佔用的位元組。slices_name 的類型是 &str (一個字串切片引用)。它指向 full_name 所擁有的堆積記憶體中 "Chen" 這部分數據。而slices_name 並不擁有這些數據,它只是借用了它們。fn main() {
let first_name = {
let name = "Chen Roger"; // name 是字串字面值,型別為 &str
&name[0..4] // 從 &str 建立另一個 &str 切片
}; // 雖然 name 在內部作用域,但 first_name 仍然有效,因為是借用靜態字串
println!("{}", first_name); // 印出 "Chen"
}
let name = "Chen Roger";
&'static str。&str 表示它是一個字串切片。'static (靜態生命週期) 表示這個字串數據是直接嵌入到程式的二進制文件中的,並且在程式的整個運行期間都有效。所以,變數 name 是一個指向這個靜態儲存區域的引用。name 變數離開作用域,因為它是靜態資料,所以 first_name 仍然有效。let first_name = { ... &name[0..4] };
&name[0..4]:
[0..4] 來從 name 所引用的字串數據中提取一部分。name 本身已經是一個 &'static str (一個引用),對它進行切片操作 name[0..4] 會得到一個概念上的 str (雖然 str 本身不能直接作為變數類型,因為它的大小不固定)。然後,我們再對這個結果取引用 &,所以 &name[0..4] 的結果仍然是一個 &str。name 指向的是具有 'static 生命週期的數據,所以 first_name 得到的切片也將具有 'static 生命週期,即 first_name 的類型也是 &'static str。它指向靜態記憶體中 "Chen" 這部分。slices_name: &str 依賴於 full_name: String 的存在。如果 full_name 被銷毀,slices_name 就會變成一個懸垂引用(Rust 的借用檢查器會阻止這種情況發生)。first_name: &'static str 是在一個內部作用域中根據 name: &'static str 創建的。當內部作用域結束時,name 這個 變數綁定 本身確實消失了。然而,name 所指向的數據 "Chen Roger" 是一個字串字面值,它存在於程式的靜態記憶體區域,擁有 'static 生命週期,意味著它在整個程式運行期間都有效。first_name 引用的是這塊靜態記憶體中的 "Chen" 部分。儘管創建它的 name 變數 不再存在於 first_name 的作用域中,但 first_name 所引用的 數據 仍然有效。所以,first_name 依賴的是那塊靜態儲存的字串數據,而不是 name 這個局部變數的持續存在。fn main() {
let moji = "🤣"; // 單一 emoji
println!("{}", moji.len()); // 輸出 4(UTF-8 編碼為 4 bytes)
// 下行程式會造成 panic,因為索引切到不完整的 UTF-8 字元
// let moji_slice = &moji[0..3];
// println!("{}", moji_slice);
}
String 與 &str 的 .len() 回傳的是位元組長度,而非字元數量。fn main() {
let hello = "hello";
println!("長度為:{}", hello.len()); // 輸出 5,因為每個字母是 1 byte
let zh = "哈囉";
println!("長度為:{}", zh.len()); // 輸出 6,每個中文字是 3 bytes
}
fn main() {
let action_hero = String::from("Arnold Schwarzenegger");
let first_name = &action_hero[..6];
println!("His first name is {first_name}.");
let last_name = &action_hero[7..];
println!("His last name is {last_name}.");
let full_name = &action_hero[..];
println!("His full name is {full_name}.");
}
[..6] 代表從開頭到第 6 個 byte。[7..] 代表從第 7 個 byte 到字串結尾。[..] 表示整個字串的切片。&action_hero[..] 與 &action_hero 的差異在&action_hero[..]中,它的意思是選取整個字串,從開頭到末尾。因為是個&str類型,所以創建了一個指向 action_hero 所擁有的實際字串數據(儲存在堆積上的字元序列)的切片。這個切片覆蓋了 action_hero 的全部內容。&str 是一個「胖指針」(fat pointer),它包含兩個部分:一個指向字串數據起始位置的 指針 (pointer)、切片的 長度 (length),以字節為單位。
因此,當你想要一個不可變的、對字串內容的直接引用時,通常使用 &str。很多處理字串數據的函式都接受 &str 作為參數,因為它更通用(既可以引用 String 的一部分或全部,也可以引用字串字面值)。
&action_hero,其型別是&String (一個對 String 結構體的引用),而String 結構體是一個管理者,它內部包含了指向堆上字串數據的指針,以及該數據的長度和容量 (capacity)。內部結構 (被引用的 String 結構體)中,包含:一個指向堆積上實際字串數據的 指針 (pointer)、字串的目前 長度 (length)、堆上緩衝區的總 容量 (capacity)。當你需要引用整個 String 物件,並且可能需要存取 String 特有的方法或其長度/容量等元數據時,你會使用 &String。
本質不同:
&action_hero[..] (&str) 是一個對 字串數據本身 的直接視圖/切片。&action_hero (&String) 是一個對 管理字串數據的結構體 的引用。資訊含量:
&str 直接告訴你數據在哪裡以及有多長。&String 告訴你 String 這個「容器」在哪裡,你可以透過這個容器找到數據以及容器的容量等資訊。這節的主軸是以「字串切片作為函式參數」(String Slice as Function Parameters),並說明 &String 和 &str 之間的轉換關係。在 Rust 中,當你編寫一個需要讀取字串數據但不獲取其所有權的函式時,通常最符合習慣且最具彈性的做法是讓參數型別為 &str(字串切片)。
fn main() {
let action_hero = String::from("Arnold Schwarzenegger");
do_hero_stuff(&action_hero); // 傳入 &String(自動轉為 &str)
let other_action_hero = "Sylvester Stallone"; // 字串字面值為 &str
do_hero_stuff(other_action_hero);
}
fn do_hero_stuff(the_hero: &str) {
println!("{} saves the day.", the_hero);
}
&String 給 &str 參數 ( &String -> &str )do_hero_stuff(&action_hero);,在這行程式碼中,&action_hero創建了一個對String物件action_hero的引用,因此它的型別是&String。但是,在 do_hero_stuff 函式期望的參數型別是 &str。而這個函式能成功轉換是因為 Rust 的一個重要特性叫做 Deref Coercion (解引用轉換)。因為String 型別實作了 Deref <Target = str> Trait。這個 Trait 允許 String 物件(或者更準確地說,是對 String 的引用)在需要時被自動「解引用」成 str 型別的引用(即 &str)。所以,當你將 &String 傳遞給一個 &str 的函式時,編譯器會自動進行轉換,使得 &action_hero 就像一個指向其內部字串數據的 &str 切片一樣被使用。這實際上等同於傳遞了 &action_hero[..] 或 action_hero.as_str()。&str (字串字面值) 給 &str 參數do_hero_stuff(other_action_hero);,在這行程式碼中,other_action_hero本身即為一個&str,它與 do_hero_stuff 函式參數 the_hero: &str 的型別直接匹配。因此,這裡不需要任何特殊的轉換,可以直接傳遞。&str -> &String 呢?&str 不是 heap 所有者,不能轉為 &String
| 類型來源 | 型別 | 可否傳給 &str 函式? |
備註 |
|---|---|---|---|
| 字串字面值 | &'static str |
✅ 是 | 最常見,也最安全 |
String 變數 |
String |
✅ 是(需加 &) |
&String 會自動轉為 &str |
&String |
&String |
✅ 是 | 自動解參照為 &str |
&str |
&str |
✅ 是 | 完全相容 |
&str → String |
❌ 不能直接轉 | 可用 .to_string() 或 .into() |
透過使用 &str 作為函式參數,可以讓你的 API 更通用,同時支援 String 與字串字面值,大幅增加彈性與效率。